package com.gadgets.busi.bm02.service;

import com.alibaba.fastjson.JSON;
import com.gadgets.busi.bm02.common.GadgetsBm02Constant;
import com.gadgets.busi.bm02.common.GadgetsBm02Utils;
import com.gadgets.busi.bm02.dto.RecipeQueryDTO;
import com.gadgets.busi.bm02.dto.RecipeWeightQueryDTO;
import com.gadgets.busi.bm02.pojo.MaterialDict;
import com.gadgets.busi.bm02.pojo.MaterialRecipe;
import com.gadgets.busi.bm02.pojo.MaterialRecipeWeight;
import com.gadgets.busi.bm02.repository.IMaterialDictRepository;
import com.gadgets.busi.bm02.repository.IMaterialRecipeRepository;
import com.gadgets.busi.bm02.repository.IMaterialRecipeWeightRepository;
import com.gadgets.busi.dataanalysis.registry.task.DataAnalysisClusterRunnable;
import com.gadgets.busi.dataanalysis.registry.task.DataAnalysisSeekRunnable;
import com.gadgets.framework.common.dto.SynergyLockQueryDTO;
import com.gadgets.framework.common.dto.SynergyRedisLock;
import com.gadgets.framework.common.exception.GadgetsServiceException;
import com.gadgets.framework.common.repository.ISynergyRepository;
import com.gadgets.framework.common.util.RedisUtil;
import com.gadgets.framework.common.util.StringUtils;
import com.gadgets.framework.common.util.ThreadTaskUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author UncleXiu
 * @version V1.0.0
 * @data 2020-12-16
 * 
 */
@Service
@Transactional(transactionManager = "gadgetsDbTransactionManage", rollbackFor = {Throwable.class})
public class DataAnalysisServiceImpl implements IMaterialAnalysisService {

    private static final Logger logger = LoggerFactory.getLogger(DataAnalysisServiceImpl.class);

    /**
     * 字典查询器
     */
    @Autowired
    private IMaterialDictRepository dictRepository;
    /**
     * 合成表查询器
     */
    @Autowired
    private IMaterialRecipeRepository recipeRepository;
    /**
     * 合成权值查询器
     */
    @Autowired
    private IMaterialRecipeWeightRepository recipeWeightRepository;
    /**
     * 缓存对象
     */
    @Autowired
    private GadgetsBm02Utils gadgetsBm02Utils;
    /**
     * 同步锁获取器
     */
    @Autowired
    private ISynergyRepository synergyRepository;

    @Autowired
    private RedisUtil redisUtil;
    /**
     * 数据聚合容器
     */
    private final ConcurrentHashMap<Long, HashMap<String,List<MaterialRecipe>>> clusterContainer = new ConcurrentHashMap<>();
    /**
     * 同步锁获取器
     */
    private static final SynergyLockQueryDTO QUERY_DTO;

    static {
        QUERY_DTO = new SynergyLockQueryDTO();
        QUERY_DTO.setRequestPath(GadgetsBm02Constant.DA_MODUEL_NO);
    }

    /**
     * 合成权分析任务
     *
     * @throws GadgetsServiceException 异常信息
     */
    @Transactional(transactionManager = "gadgetsDbTransactionManage", rollbackFor = {Throwable.class})
    @Override
    public void analysisBuildWeight() throws GadgetsServiceException, InterruptedException {
        // 只有能够获取锁，才能执行
        SynergyRedisLock lock = new SynergyRedisLock(QUERY_DTO.getRequestPath(), "1", 6000);
        if(redisUtil.setNxSyncLock(lock)) {
            try {
                logger.trace("数据聚合开始");
                RecipeQueryDTO queryDTO = new RecipeQueryDTO();
                queryDTO.addAsc("ID");
                List<MaterialRecipe> recipeList = recipeRepository.selectMaterialRecipeByCondition(null);
                List<DataAnalysisClusterRunnable> dataClusterContainer = new ArrayList<>();
                HashMap<String, Integer> taskCount = new HashMap<>();
                String dataTime = System.currentTimeMillis() + "|/";
                // 分割-提交线程任务信息
                for (int i = 1; i < 4; i++) {
                    buildDataClusterTask(recipeList, dataClusterContainer, dataTime, i, taskCount);
                }
                logger.trace("数据聚合任务已提交");
                // 收集线程执行结果
                dataClusterResultExcuter(recipeList, dataClusterContainer, taskCount, dataTime);
                logger.info("数据聚合任务已完成");
                logger.trace("数据分析开始");
                // 分析数据
                List<DataAnalysisSeekRunnable> seekTaskCount = new ArrayList<>();
                int seekCount = 0;
                for (Map.Entry<Long, HashMap<String, List<MaterialRecipe>>> materEntry : clusterContainer.entrySet()) {
                    seekCount = buildSeekTask(seekTaskCount, seekCount, materEntry);
                }
                logger.trace("数据分析任务已提交");
                // 解析最小权值数据
                analysisMinWeightResult(seekTaskCount);
                logger.info("数据分析任务已完成");
                // 清空聚合数据集
                clusterContainer.clear();
            } finally {
                redisUtil.deleteSyncLock(lock);
            }
        }else {
            logger.warn("MC批量任务未能获取到锁，已有其他线程开始执行相关代码，跳过本次执行");
        }
    }

    /**
     * 构建最小权计算任务
     * @param seekTaskCount             任务计数器
     * @param seekCount                 任务数
     * @param materEntry                数据集
     * @return                          应答信息
     * @throws InterruptedException     异常信息
     */
    protected int buildSeekTask(List<DataAnalysisSeekRunnable> seekTaskCount, int seekCount, Map.Entry<Long, HashMap<String, List<MaterialRecipe>>> materEntry) throws InterruptedException {
        int threadTaskSize = 30;
        if(seekCount % threadTaskSize==0){
            // 构建并提交最小权计算任务
            DataAnalysisSeekRunnable seekRunnable = new DataAnalysisSeekRunnable(gadgetsBm02Utils, this);
            seekRunnable.addWorkList(materEntry);
            seekTaskCount.add(seekRunnable);
            // 如果当前任务是最后一项任务，就直接提交线程池
            if(seekCount == clusterContainer.size()-1){
                ThreadTaskUtil.submit(seekRunnable);
            }
        }else {
            DataAnalysisSeekRunnable seekRunnable = seekTaskCount.get(seekTaskCount.size()-1);
            seekRunnable.addWorkList(materEntry);
            // 如果已经提交10项任务，或者是最后一项任务，就提交线程池
            if(seekCount % threadTaskSize==(threadTaskSize-1) || seekCount == clusterContainer.size()-1){
                ThreadTaskUtil.submit(seekRunnable);
            }
        }
        seekCount++;
        return seekCount;
    }

    /**
     * 合成表权值计算模型
     * @param recipeList    合成表信息
     * @return              应答信息
     */
    @Transactional(transactionManager = "gadgetsDbTransactionManage", rollbackFor = {Throwable.class})
    public Double recipeWeightModuel(List<MaterialRecipe> recipeList) throws IOException {
        double weight = 0.0D;
        Double energyPower = recipeList.get(0).getEnergyNum();
        // 耗能量
        if(null==energyPower){
            energyPower = 0.0D;
        }
        // 产量权，用户降低高产量合成表的权，以推荐产量更大的合成表
        double goodNum = Math.pow(recipeList.get(0).getGoodNum(),1.05);
        // 原料加权
        for (MaterialRecipe recipe : recipeList) {
            MaterialDict itemDict = gadgetsBm02Utils.getMcMeterialDictItem(recipe.getMateriaId());
            weight += itemDict.getWeight() * recipe.getDemandNum() / goodNum;
        }
        logger.debug("__________________________________________________");
        logger.debug("原料权：{}",weight);
        // 合成步骤加权
        double materialTypeWeight = Double.parseDouble(
                GadgetsBm02Utils.getBm02DaProperties(
                        GadgetsBm02Constant.MATERIAL_TYPE_WEIGHT.valueOf("T"+recipeList.size()).getPropName()
                )
        );
        logger.debug("原料种类权：{}",materialTypeWeight);
        double energyWeight = 1.0 + energyPower/goodNum/32.0 + energyPower/goodNum/Math.pow(32,1.3) + materialTypeWeight;
        logger.debug("耗能权：{}",energyWeight);
        weight = weight + Math.pow(energyWeight,0.5D);
        logger.debug("最终合成权：{}",weight);
        logger.debug("__________________________________________________");
        // 保存 或 更新最小权
        RecipeWeightQueryDTO queryDto = new RecipeWeightQueryDTO();
        queryDto.setBatchNo(recipeList.get(0).getBatchNo());
        queryDto.setRecipePolicy(GadgetsBm02Constant.MC_RECIPE_WEIGHT_POLICY.MIN_GATEWAY);
        List<MaterialRecipeWeight> weightList = recipeWeightRepository.queryRecipeBatchByCondition(queryDto);
        if(null!=weightList && weightList.size()==1){
            // 已存在权数据
            MaterialRecipeWeight recipeWeight = weightList.get(0);
            recipeWeight.setWeight(weight);
            recipeWeightRepository.updateMaterialWeightInfo(recipeWeight);
        }else {
            try {
                // 不存在权数据
                MaterialRecipeWeight recipeWeight = new MaterialRecipeWeight();
                recipeWeight.setBatchNo(recipeList.get(0).getBatchNo());
                recipeWeight.setDictId(recipeList.get(0).getTargetMeterialId());
                recipeWeight.setPolicy(GadgetsBm02Constant.MC_RECIPE_WEIGHT_POLICY.MIN_GATEWAY);
                recipeWeight.setWeight(weight);
                recipeWeightRepository.saveMaterialRecipeWeightInfo(recipeWeight);
            } catch (Exception exception) {
                // 如果新增失败，则需要记录数据，缓存数据可以继续使用
                logger.error(StringUtils.formatExceptionStack(exception));
            }
        }
        return weight;
    }

    protected void analysisMinWeightResult(List<DataAnalysisSeekRunnable> seekTaskCount) {
        while (seekTaskCount.size()!=0){
            Iterator<DataAnalysisSeekRunnable> seekRunnableIterator = seekTaskCount.iterator();
            int i = 1;
            while (seekRunnableIterator.hasNext()){
                DataAnalysisSeekRunnable seekRunnable = seekRunnableIterator.next();
                if (-1 == seekRunnable.getStatus()) {
                    // 如果线程任务执行成功
                    logger.debug("物品最小权计算任务{}执行成功", JSON.toJSONString(seekRunnable.getMaterialDictArrayList()));
                    ArrayList<MaterialDict> seekDict = seekRunnable.getMaterialDictArrayList();
                    // 向数据库更新最小权值
                    for (MaterialDict dict : seekDict) {
                        dictRepository.updateMaterialDictWeight(dict);
                    }
                    // 最小权值合成表信息
                    ConcurrentHashMap<Long, List<MaterialRecipe>> lastWeightResult = seekRunnable.getResultContainer();
                    gadgetsBm02Utils.putLigtWeightRecipeCache(lastWeightResult);
                }else if(0==seekRunnable.getStatus()){
                    continue;
                }else {
                    logger.error("物品最小权计算任务{}执行失败！", JSON.toJSONString(seekRunnable.getMaterialDictArrayList()));
                }
                // 将任务从待处理列表中剥离
                seekRunnableIterator.remove();
            }
        }
    }

    /**
     * 数据聚合结果解析
     * @param recipeList            源数据
     * @param dataClusterContainer  聚合任务容器
     * @param taskCount             任务计数器
     * @param dataTime              任务创建时间
     */
    protected void dataClusterResultExcuter(List<MaterialRecipe> recipeList, List<DataAnalysisClusterRunnable> dataClusterContainer, HashMap<String, Integer> taskCount, String dataTime) {
        // 循环校验数据聚合结果
        while (dataClusterContainer.size()!=0) {
            Iterator<DataAnalysisClusterRunnable> runnableIterator = dataClusterContainer.iterator();
            int i = 1;
            while (runnableIterator.hasNext()){
                DataAnalysisClusterRunnable runnable = runnableIterator.next();
                if (-1 == runnable.getStatus()) {
                    // 如果线程任务执行成功
                    logger.info("数据聚合任务{}执行成功",runnable.getTaskNo());
                    runnableIterator.remove();
                    buildClusterResult(runnable);
                } else if (1 == runnable.getStatus()) {
                    // 如果任务执行失败
                    logger.warn("数据聚合任务{}执行失败",runnable.getTaskNo());
                    runnableIterator.remove();
                    // 尝试重新提交任务
                    if(taskCount.get(dataTime +i)<3) {
                        logger.warn("尝试重新提交数据聚合任务");
                        buildDataClusterTask(recipeList, dataClusterContainer, dataTime, i, taskCount);
                    }else {
                        // 任务失败超过3次，就不再提交。保留错误提醒日志
                        logger.error("聚合任务{}已尝试重新执行3次，仍未成功，跳过数据聚合", dataTime +i);
                    }
                }
                // 任务计数器+1
                i++;
                // 任务尚未完成，不作任何处理
            }
        }
    }

    /**
     * 组装数据聚合
     * @param runnable  数据聚合线程任务
     */
    protected void buildClusterResult(DataAnalysisClusterRunnable runnable) {
        HashMap<Long, HashMap<String,List<MaterialRecipe>>> clusterDate = runnable.getClusterResult();
        for (Map.Entry<Long, HashMap<String, List<MaterialRecipe>>> entry : clusterDate.entrySet()) {
            if(!clusterContainer.containsKey(entry.getKey())){
                // 如果这个物品的合成表从来没有过，直接整体插入
                clusterContainer.put(entry.getKey(), entry.getValue());
            }else {
                // 如果这个物品的信息存在，那么就需要循环插入合成表
                HashMap<String, List<MaterialRecipe>> recipeMap = entry.getValue();
                for (Map.Entry<String, List<MaterialRecipe>> itemEntry : recipeMap.entrySet()) {
                    if(!clusterContainer.get(entry.getKey()).containsKey(itemEntry.getKey())){
                        // 如果当前物品的当前合成表不存在，直接整体插入
                        clusterContainer.get(entry.getKey()).put(itemEntry.getKey(), itemEntry.getValue());
                    }else {
                        // 如果当前物品的当前合成表已经存在，则增量插入
                        clusterContainer.get(entry.getKey()).get(itemEntry.getKey()).addAll(itemEntry.getValue());
                    }
                }
            }
        }
    }

    /**
     * 提交线程聚合任务
     * @param recipeList            总数据集合
     * @param dataClusterContainer  任务容器
     * @param dataTime              任务时间戳
     * @param i                     任务序号
     */
    protected void buildDataClusterTask(List<MaterialRecipe> recipeList, List<DataAnalysisClusterRunnable> dataClusterContainer, String dataTime, int i,HashMap<String,Integer> taskCount) {
        String taskNo = dataTime + i;
        DataAnalysisClusterRunnable clusterRunnable = new DataAnalysisClusterRunnable(recipeList.subList((i - 1) * recipeList.size() / 3, i * recipeList.size() / 3));
        clusterRunnable.setTaskNo(taskNo);
        ThreadTaskUtil.submit(clusterRunnable);
        dataClusterContainer.add(clusterRunnable);
        if(!taskCount.containsKey(taskNo)){
            taskCount.put(taskNo,0);
        }
        taskCount.put(taskNo,taskCount.get(taskNo)+1);
    }

}
